/*
 *  linux/init/main.c
 *
 *  (C) 1991  Linus Torvalds
 */

#define __LIBRARY__
#include <unistd.h>
#include <time.h>

/*
 * we need this inline - forking from kernel space will result
 * in NO COPY ON WRITE (!!!), until an execve is executed. This
 * is no problem, but for the stack. This is handled by not letting
 * main() use the stack at all after fork(). Thus, no function
 * calls - which means inline code for fork too, as otherwise we
 * would use the stack upon exit from 'fork()'.
 *
 * Actually only pause and fork are needed inline, so that there
 * won't be any messing with the stack from main(), but we define
 * some others too.
 */
static inline _syscall0(int,fork)
static inline _syscall0(int,pause)
static inline _syscall1(int,setup,void *,BIOS)
static inline _syscall0(int,sync)

#include <linux/tty.h>
#include <linux/sched.h>
#include <linux/head.h>
#include <asm/system.h>
#include <asm/io.h>

#include <stddef.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

#include <linux/fs.h>

static char printbuf[1024];

extern int vsprintf();
extern void init(void);
extern void blk_dev_init(void);
extern void chr_dev_init(void);
extern void hd_init(void);
extern void floppy_init(void);
// 位于 memory.c 文件中
extern void mem_init(long start, long end);
extern long rd_init(long mem_start, int length);
extern long kernel_mktime(struct tm * tm);
extern long startup_time;

/*
 * This is set up by the setup-routine at boot-time
 */
#define EXT_MEM_K (*(unsigned short *)0x90002)
#define DRIVE_INFO (*(struct drive_info *)0x90080)
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)

/*
 * Yeah, yeah, it's ugly, but I cannot find how to do this correctly
 * and this seems to work. I anybody has more info on the real-time
 * clock I'd be interested. Most of this was trial and error, and some
 * bios-listing reading. Urghh.
 */

#define CMOS_READ(addr) ({ \
outb_p(0x80|addr,0x70); \
inb_p(0x71); \
})

#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)

static void time_init(void)
{
	struct tm time;

	do {
		time.tm_sec = CMOS_READ(0);
		time.tm_min = CMOS_READ(2);
		time.tm_hour = CMOS_READ(4);
		time.tm_mday = CMOS_READ(7);
		time.tm_mon = CMOS_READ(8);
		time.tm_year = CMOS_READ(9);
	} while (time.tm_sec != CMOS_READ(0));
	BCD_TO_BIN(time.tm_sec);
	BCD_TO_BIN(time.tm_min);
	BCD_TO_BIN(time.tm_hour);
	BCD_TO_BIN(time.tm_mday);
	BCD_TO_BIN(time.tm_mon);
	BCD_TO_BIN(time.tm_year);
	time.tm_mon--;
    // kernel_mktime 也很简单，就是根据刚刚的那些时分秒数据，计算从 1970 年 1 月 1 日 0 时起到开机当时经过的秒数，作为开机时间，存储在 startup_time 这个变量里
	startup_time = kernel_mktime(&time);
}

static long memory_end = 0;
static long buffer_memory_end = 0;
static long main_memory_start = 0;

struct drive_info { char dummy[32]; } drive_info;

void main(void)		/* This really IS void, no error here. */
{			/* The startup routine assumes (well, ...) this */
/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
/**
 * setup.s 这个汇编程序调用 BIOS 中断获取的各个设备的信息，并保存在约定好的内存地址 0x90000 处
 *  内存地址	 长度(字节)	 名称
    0x90000	  2	         光标位置
    0x90002	  2          扩展内存数
    0x90004	  2	         显示页面
    0x90006	  1          显示模式
    0x90007	  1	         字符列数
    0x90008	  2	         未知
    0x9000A	  1          显示内存
    0x9000B	  1          显示状态
    0x9000C	  2	         显卡特性参数
    0x9000E	  1          屏幕行数
    0x9000F	  1	         屏幕列数
    0x90080	  16         硬盘1参数表
    0x90090	  16	     硬盘2参数表
    0x901FC	  2          根设备号
 */
    // 根设备号 (*(int*)0xXXXX),先转换成指针，再取值
 	ROOT_DEV = ORIG_ROOT_DEV;
    // 先将 第一块硬盘地址 0x90080 转换成结构体指针，然后改结构体指针包含32个字节，一个硬盘参数有16个字节，所以这里包含了两个硬盘参数
 	drive_info = DRIVE_INFO;
    // 1<<20 = 1M,1<<10 = 1KB
    // EXT_MEM_K 扩展内存数 ，1M+ 扩展内存数* 1KB
	memory_end = (1<<20) + (EXT_MEM_K<<10);
    // 0xfffff000 高20位是1， 低12位是0，1<<12=4KB， 分页的时候一页也是4KB，所以将 memory_end 地址按 4KB 对齐。强制将 memory_end 的低12位清零
    // 计算内存大于4kb的整数倍，但是也不要多余的，主要是为了内存对其
    // 1024=1<<10
	memory_end &= 0xfffff000;
    // 如果内存大于16M，就设置内存边界最大 16M ，超过的操作系统不会访问
	if (memory_end > 16*1024*1024)
		memory_end = 16*1024*1024;
    // 如果内存大于12M，
	if (memory_end > 12*1024*1024)
        // 同时设置 buffer内存边界最大是 4M
		buffer_memory_end = 4*1024*1024;
	else if (memory_end > 6*1024*1024)
        // 如果内存大于6M，就设置buffer内存为2M，相当于是缓存吧
		buffer_memory_end = 2*1024*1024;
	else
        // 如果内存小于6M，就设置buffer内存是1M
		buffer_memory_end = 1*1024*1024;
    // 设置主程序开始内存是 buffer内存结束
	main_memory_start = buffer_memory_end;
    // 上面的步骤 设置了内存最大16M，已经buffer内存边界和主程序的内存边界
#ifdef RAMDISK
	main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
    // 主内存区的管理和分配
	mem_init(main_memory_start,memory_end);
    // traps.c
    // 初始化中断描述符表
	trap_init();
    // ll_rw_blk.c
    // 初始化块设备请求等等。。。
	blk_dev_init();
	chr_dev_init();
    // tty_io.c
    // 初始化 显示器等等，以便用户可以在控制台输入等等，其中console.c 最重要了
	tty_init();
    // 通过与 CMOS 端口进行读写交互，获取到了年月日时分秒等数据，并通过这些计算出了开机时间 startup_time 变量，是从 1970 年 1 月 1 日 0 时起到开机当时经过的秒数
	time_init();
	sched_init();
    // 缓冲区的管理和分配
	buffer_init(buffer_memory_end);
    // 硬盘初始化
	hd_init();
	floppy_init();
	sti();
    // 转变为用户态模式
	move_to_user_mode();
    // static inline _syscall0(int,fork)
    /**
      #define _syscall0(type,name) \
        type name(void) \
        { \
        long __res; \
        __asm__ volatile ("int $0x80" \
            : "=a" (__res) \
            : "0" (__NR_##name)); \
        if (__res >= 0) \
            return (type) __res; \
        errno = -__res; \
        return -1; \
        }
     */
     // 宏展开就是一个函数
     /**
      int fork(void) {
         volatile long __res;
        _asm {
            _asm mov eax,__NR_fork // 2 #define __NR_fork	2
            _asm int 80h           // 系统调用中断
            _asm mov __res,eax
        }
        if (__res >= 0)
            return (void) __res;
        errno = -__res;
        return -1;
    }
      */
      // 调用系统调用中断，调用 system_call 函数，在system_call.s 汇编中
      /**
       _system_call:
        cmpl $nr_system_calls-1,%eax
        ja bad_sys_call
        push %ds
        push %es
        push %fs
        pushl %edx
        pushl %ecx		# push %ebx,%ecx,%edx as parameters
        pushl %ebx		# to the system call
        movl $0x10,%edx		# set up ds,es to kernel space
        mov %dx,%ds
        mov %dx,%es
        movl $0x17,%edx		# fs points to local data space
        mov %dx,%fs
        // 这里的eax 就等于上面的 __NR_fork // 2 #define __NR_fork	2
        call _sys_call_table(,%eax,4)
        pushl %eax
        movl _current,%eax
        cmpl $0,state(%eax)		# state
        jne reschedule
        cmpl $0,counter(%eax)		# counter
        je reschedule
       */
       // sys.h 中的 sys_call_table 中第二个就是 sys_fork
       /**
        * fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
            sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
            sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
            sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
            sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
            sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
            sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
            sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
            sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
            sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
            sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
            sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
            sys_setreuid,sys_setregid };
        */
        // 这个 sys_fork函数 在system_call.s 汇编中
        /**
         _sys_fork:
            call _find_empty_process
            testl %eax,%eax
            js 1f
            push %gs
            pushl %esi
            pushl %edi
            pushl %ebp
            pushl %eax
            call _copy_process
            addl $20,%esp
        1:	ret
         */
         // 在汇编函数 _sys_fork 中调用c语言中的 copy_process 实现fork函数效果逻辑
         // return last_pid;
         // 记录最新分配的 PID：
         // 每次创建新进程（如 fork() 或 clone()）时，内核基于 last_pid 递增分配新的 PID。
         // 避免 PID 重复：
         // 通过递增 last_pid 确保短时间内 PID 唯一性（需结合循环回收机制）。
	if (!fork()) {		/* we count on this going ok */
		init();
	}
/*
 *   NOTE!!   For any other task 'pause()' would mean we have to get a
 * signal to awaken, but task0 is the sole exception (see 'schedule()')
 * as task 0 gets activated at every idle moment (when no other tasks
 * can run). For task0 'pause()' just means we go check if some other
 * task can run, and if not we return here.
 */
	for(;;) pause();
}

static int printf(const char *fmt, ...)
{
	va_list args;
	int i;

	va_start(args, fmt);
	write(1,printbuf,i=vsprintf(printbuf, fmt, args));
	va_end(args);
	return i;
}

static char * argv_rc[] = { "/bin/sh", NULL };
static char * envp_rc[] = { "HOME=/", NULL };

static char * argv[] = { "-/bin/sh",NULL };
static char * envp[] = { "HOME=/usr/root", NULL };

void init(void)
{
	int pid,i;

    // static inline _syscall1(int,setup,void *,BIOS)
    // 系统调用，sys_setup()
    // 在 hd.c文件中实现
    // setup 是个系统调用，会通过中断最终调用到 sys_setup 函数
    // drive_info 是来自内存 0x90080 的数据
	setup((void *) &drive_info);
    // open 函数会触发 0x80 中断，最终调用到 sys_open 这个系统调用函数
    // 在 fs/open.c中
    // open 函数返回的为 0 号 fd，这个作为标准输入设备。
	(void) open("/dev/tty0",O_RDWR,0);
    // 在 fs/fcntl.c中
    // dup 为 1 号 fd 赋值，这个作为标准输出设备。
	(void) dup(0);
    // dup 为 2 号 fd 赋值，这个作为标准错误输出设备。
	(void) dup(0);
	printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
		NR_BUFFERS*BLOCK_SIZE);
	printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);

    // 到此为止，标志着进程 1 的工作基本结束了，准确说是能力建设的工作结束了，接下来就是控制流程和创建新的进程了，
    // 一个以 rc 为标准输入的 shell
    // 启动第一个 Shell（带 /etc/rc 脚本）
    // 如果 /etc/rc 存在，则将其作为 shell 的输入（早期初始化脚本）
	if (!(pid=fork())) {
		close(0);
		if (open("/etc/rc",O_RDONLY,0))
			_exit(1);
        // int execve(const char * filename, char ** argv, char ** envp);
        // 最终执行 fs/exec.c
        // static char * argv_rc[] = { "/bin/sh", NULL };
        // static char * envp_rc[] = { "HOME=/", NULL };
		execve("/bin/sh",argv_rc,envp_rc);
		_exit(2);
	}
    // fork（）进程后，父进程和子进程大部分内容一样，包括ip，cp寄存器，代码段等等，只不过eax里面的值不一样
    // 父进程中的eax是子进程的 pid，子进程的eax 被设置 0，所以当程序调度到子进程时，子进程的ip，cs由于和父进程一样，所以会从fork函数的下一行代码执行
    // 然后函数返回值存在eax中，所以子进程的fork返回值是 0
    // 父进程等待子进程结束
	if (pid>0)
		while (pid != wait(&i))
			/* nothing */;
    // 主循环：常驻 Shell 守护
    // 如果第一个 Shell 退出（或没有 /etc/rc），进入主循环：
	while (1) {
        // fork() 创建新的 Shell 进程
		if ((pid=fork())<0) {
			printf("Fork failed in init\r\n");
			continue;
		}
        // 如果程序调度到子进程中
		if (!pid) {
            // // 关闭所有标准流
			close(0);close(1);close(2);
            // // 创建新会话（脱离终端控制）
			setsid();
            // 重新打开终端
			(void) open("/dev/tty0",O_RDWR,0);
            // 重置标准输出/错误
			(void) dup(0);
			(void) dup(0);
            // 启动新 shell
			_exit(execve("/bin/sh",argv,envp));
		}
		while (1)
            // 父进程通过 wait(&i) 监控子进程退出，打印死亡信息后重启 Shell。
			if (pid == wait(&i))
				break;
		printf("\n\rchild %d died with code %04x\n\r",pid,i);
        // 确保文件系统同步，防止数据丢失。
		sync();
	}
	_exit(0);	/* NOTE! _exit, not exit() */
}
